/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * 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/. */

/*
 * Dialog services for PIP.
 */

#include "nsNSSDialogs.h"

#include "mozIDOMWindow.h"
#include "nsArray.h"
#include "nsEmbedCID.h"
#include "nsHashPropertyBag.h"
#include "nsIDialogParamBlock.h"
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIKeygenThread.h"
#include "nsIPromptService.h"
#include "nsIProtectedAuthThread.h"
#include "nsIWindowWatcher.h"
#include "nsIX509CertDB.h"
#include "nsIX509Cert.h"
#include "nsNSSDialogHelper.h"
#include "nsString.h"
#include "nsVariant.h"

#define PIPSTRING_BUNDLE_URL "chrome://pippki/locale/pippki.properties"

nsNSSDialogs::nsNSSDialogs()
{
}

nsNSSDialogs::~nsNSSDialogs()
{
}

NS_IMPL_ISUPPORTS(nsNSSDialogs, nsITokenPasswordDialogs,
                  nsICertificateDialogs,
                  nsIClientAuthDialogs,
                  nsITokenDialogs,
                  nsIGeneratingKeypairInfoDialogs)

nsresult
nsNSSDialogs::Init()
{
  nsresult rv;

  nsCOMPtr<nsIStringBundleService> service =
           do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
  if (NS_FAILED(rv)) return rv;
  
  rv = service->CreateBundle(PIPSTRING_BUNDLE_URL,
                             getter_AddRefs(mPIPStringBundle));
  return rv;
}

nsresult
nsNSSDialogs::SetPassword(nsIInterfaceRequestor *ctx,
                          const char16_t *tokenName, bool* _canceled)
{
  nsresult rv;

  *_canceled = false;

  // Get the parent window for the dialog
  nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx);

  nsCOMPtr<nsIDialogParamBlock> block =
           do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID);
  if (!block) return NS_ERROR_FAILURE;

  rv = block->SetString(1, tokenName);
  if (NS_FAILED(rv)) return rv;

  rv = nsNSSDialogHelper::openDialog(parent,
                                "chrome://pippki/content/changepassword.xul",
                                block);

  if (NS_FAILED(rv)) return rv;

  int32_t status;

  rv = block->GetInt(1, &status);
  if (NS_FAILED(rv)) return rv;

  *_canceled = (status == 0)?true:false;

  return rv;
}

NS_IMETHODIMP
nsNSSDialogs::ConfirmDownloadCACert(nsIInterfaceRequestor* ctx,
                                    nsIX509Cert* cert,
                            /*out*/ uint32_t* trust,
                            /*out*/ bool* importConfirmed)
{
  // |ctx| is allowed to be null.
  NS_ENSURE_ARG(cert);
  NS_ENSURE_ARG(trust);
  NS_ENSURE_ARG(importConfirmed);

  nsCOMPtr<nsIMutableArray> argArray = nsArrayBase::Create();
  if (!argArray) {
    return NS_ERROR_FAILURE;
  }

  nsresult rv = argArray->AppendElement(cert, false);
  if (NS_FAILED(rv)) {
    return rv;
  }

  nsCOMPtr<nsIWritablePropertyBag2> retVals = new nsHashPropertyBag();
  rv = argArray->AppendElement(retVals, false);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // Get the parent window for the dialog
  nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx);
  rv = nsNSSDialogHelper::openDialog(parent,
                                     "chrome://pippki/content/downloadcert.xul",
                                     argArray);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("importConfirmed"),
                                  importConfirmed);
  if (NS_FAILED(rv)) {
    return rv;
  }

  *trust = nsIX509CertDB::UNTRUSTED;
  if (!*importConfirmed) {
    return NS_OK;
  }

  bool trustForSSL = false;
  rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("trustForSSL"),
                                  &trustForSSL);
  if (NS_FAILED(rv)) {
    return rv;
  }
  bool trustForEmail = false;
  rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("trustForEmail"),
                                  &trustForEmail);
  if (NS_FAILED(rv)) {
    return rv;
  }
  bool trustForObjSign = false;
  rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("trustForObjSign"),
                                  &trustForObjSign);
  if (NS_FAILED(rv)) {
    return rv;
  }

  *trust |= trustForSSL ? nsIX509CertDB::TRUSTED_SSL : 0;
  *trust |= trustForEmail ? nsIX509CertDB::TRUSTED_EMAIL : 0;
  *trust |= trustForObjSign ? nsIX509CertDB::TRUSTED_OBJSIGN : 0;

  return NS_OK;
}

NS_IMETHODIMP
nsNSSDialogs::ChooseCertificate(nsIInterfaceRequestor* ctx,
                                const nsACString& hostname,
                                int32_t port,
                                const nsACString& organization,
                                const nsACString& issuerOrg,
                                nsIArray* certList,
                        /*out*/ uint32_t* selectedIndex,
                        /*out*/ bool* certificateChosen)
{
  NS_ENSURE_ARG_POINTER(ctx);
  NS_ENSURE_ARG_POINTER(certList);
  NS_ENSURE_ARG_POINTER(selectedIndex);
  NS_ENSURE_ARG_POINTER(certificateChosen);

  *certificateChosen = false;

  nsCOMPtr<nsIMutableArray> argArray = nsArrayBase::Create();
  if (!argArray) {
    return NS_ERROR_FAILURE;
  }

  nsCOMPtr<nsIWritableVariant> hostnameVariant = new nsVariant();
  nsresult rv = hostnameVariant->SetAsAUTF8String(hostname);
  if (NS_FAILED(rv)) {
    return rv;
  }
  rv = argArray->AppendElement(hostnameVariant, false);
  if (NS_FAILED(rv)) {
    return rv;
  }

  nsCOMPtr<nsIWritableVariant> organizationVariant = new nsVariant();
  rv = organizationVariant->SetAsAUTF8String(organization);
  if (NS_FAILED(rv)) {
    return rv;
  }
  rv = argArray->AppendElement(organizationVariant, false);
  if (NS_FAILED(rv)) {
    return rv;
  }

  nsCOMPtr<nsIWritableVariant> issuerOrgVariant = new nsVariant();
  rv = issuerOrgVariant->SetAsAUTF8String(issuerOrg);
  if (NS_FAILED(rv)) {
    return rv;
  }
  rv = argArray->AppendElement(issuerOrgVariant, false);
  if (NS_FAILED(rv)) {
    return rv;
  }

  nsCOMPtr<nsIWritableVariant> portVariant = new nsVariant();
  rv = portVariant->SetAsInt32(port);
  if (NS_FAILED(rv)) {
    return rv;
  }
  rv = argArray->AppendElement(portVariant, false);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = argArray->AppendElement(certList, false);
  if (NS_FAILED(rv)) {
    return rv;
  }

  nsCOMPtr<nsIWritablePropertyBag2> retVals = new nsHashPropertyBag();
  rv = argArray->AppendElement(retVals, false);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = nsNSSDialogHelper::openDialog(nullptr,
                                     "chrome://pippki/content/clientauthask.xul",
                                     argArray);
  if (NS_FAILED(rv)) {
    return rv;
  }

  nsCOMPtr<nsIClientAuthUserDecision> extraResult = do_QueryInterface(ctx);
  if (extraResult) {
    bool rememberSelection = false;
    rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("rememberSelection"),
                                    &rememberSelection);
    if (NS_SUCCEEDED(rv)) {
      extraResult->SetRememberClientAuthCertificate(rememberSelection);
    }
  }

  rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("certChosen"),
                                  certificateChosen);
  if (NS_FAILED(rv)) {
    return rv;
  }
  if (*certificateChosen) {
    rv = retVals->GetPropertyAsUint32(NS_LITERAL_STRING("selectedIndex"),
                                      selectedIndex);
    if (NS_FAILED(rv)) {
      return rv;
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
nsNSSDialogs::SetPKCS12FilePassword(nsIInterfaceRequestor* ctx,
                            /*out*/ nsAString& password,
                            /*out*/ bool* confirmedPassword)
{
  // |ctx| is allowed to be null.
  NS_ENSURE_ARG(confirmedPassword);

  // Get the parent window for the dialog
  nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx);
  nsCOMPtr<nsIWritablePropertyBag2> retVals = new nsHashPropertyBag();
  nsresult rv =
    nsNSSDialogHelper::openDialog(parent,
                                  "chrome://pippki/content/setp12password.xul",
                                  retVals);
  if (NS_FAILED(rv)) {
    return rv;
  }

  rv = retVals->GetPropertyAsBool(NS_LITERAL_STRING("confirmedPassword"),
                                  confirmedPassword);
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (!*confirmedPassword) {
    return NS_OK;
  }

  return retVals->GetPropertyAsAString(NS_LITERAL_STRING("password"), password);
}

NS_IMETHODIMP
nsNSSDialogs::GetPKCS12FilePassword(nsIInterfaceRequestor* ctx,
                                    nsAString& _password,
                                    bool* _retval)
{
  *_retval = false;

  nsCOMPtr<nsIPromptService> promptSvc(
    do_GetService(NS_PROMPTSERVICE_CONTRACTID));
  if (!promptSvc) {
    return NS_ERROR_FAILURE;
  }

  nsAutoString msg;
  nsresult rv = mPIPStringBundle->GetStringFromName(
    u"getPKCS12FilePasswordMessage", getter_Copies(msg));
  if (NS_FAILED(rv)) {
    return rv;
  }

  // Get the parent window for the dialog
  nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx);
  bool ignored = false;
  char16_t* pwTemp = nullptr;
  rv = promptSvc->PromptPassword(parent, nullptr, msg.get(), &pwTemp, nullptr,
                                 &ignored, _retval);
  if (NS_FAILED(rv)) {
    return rv;
  }

  if (*_retval) {
    _password.Assign(pwTemp);
    free(pwTemp);
  }

  return NS_OK;
}

NS_IMETHODIMP
nsNSSDialogs::ViewCert(nsIInterfaceRequestor* ctx, nsIX509Cert* cert)
{
  // |ctx| is allowed to be null.
  NS_ENSURE_ARG(cert);

  // Get the parent window for the dialog
  nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(ctx);
  return nsNSSDialogHelper::openDialog(parent,
                                       "chrome://pippki/content/certViewer.xul",
                                       cert,
                                       false /*modal*/);
}

NS_IMETHODIMP
nsNSSDialogs::DisplayGeneratingKeypairInfo(nsIInterfaceRequestor *aCtx, nsIKeygenThread *runnable) 
{
  nsresult rv;

  // Get the parent window for the dialog
  nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(aCtx);

  rv = nsNSSDialogHelper::openDialog(parent,
                                     "chrome://pippki/content/createCertInfo.xul",
                                     runnable);
  return rv;
}

NS_IMETHODIMP
nsNSSDialogs::ChooseToken(nsIInterfaceRequestor *aCtx, const char16_t **aTokenList, uint32_t aCount, char16_t **aTokenChosen, bool *aCanceled) {
  nsresult rv;
  uint32_t i;

  *aCanceled = false;

  nsCOMPtr<nsIDialogParamBlock> block =
           do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID);
  if (!block) return NS_ERROR_FAILURE;

  block->SetNumberStrings(aCount);

  for (i = 0; i < aCount; i++) {
    rv = block->SetString(i, aTokenList[i]);
    if (NS_FAILED(rv)) return rv;
  }

  rv = block->SetInt(0, aCount);
  if (NS_FAILED(rv)) return rv;

  rv = nsNSSDialogHelper::openDialog(nullptr,
                                "chrome://pippki/content/choosetoken.xul",
                                block);
  if (NS_FAILED(rv)) return rv;

  int32_t status;

  rv = block->GetInt(0, &status);
  if (NS_FAILED(rv)) return rv;

  *aCanceled = (status == 0)?true:false;
  if (!*aCanceled) {
    // retrieve the nickname
    rv = block->GetString(0, aTokenChosen);
  }
  return rv;
}

NS_IMETHODIMP
nsNSSDialogs::DisplayProtectedAuth(nsIInterfaceRequestor *aCtx, nsIProtectedAuthThread *runnable)
{
    // We cannot use nsNSSDialogHelper here. We cannot allow close widget
    // in the window because protected authentication is interruptible
    // from user interface and changing nsNSSDialogHelper's static variable
    // would not be thread-safe
    
    nsresult rv = NS_ERROR_FAILURE;
    
    // Get the parent window for the dialog
    nsCOMPtr<mozIDOMWindowProxy> parent = do_GetInterface(aCtx);
    
    nsCOMPtr<nsIWindowWatcher> windowWatcher = 
        do_GetService("@mozilla.org/embedcomp/window-watcher;1", &rv);
    if (NS_FAILED(rv))
        return rv;
    
    if (!parent) {
        windowWatcher->GetActiveWindow(getter_AddRefs(parent));
    }
    
    nsCOMPtr<mozIDOMWindowProxy> newWindow;
    rv = windowWatcher->OpenWindow(parent,
        "chrome://pippki/content/protectedAuth.xul",
        "_blank",
        "centerscreen,chrome,modal,titlebar,close=no",
        runnable,
        getter_AddRefs(newWindow));
    
    return rv;
}
