/**
 * This function is called when username or password are requested from user.
 * This function is called in main thread as async request from dbus.
 * @param mount_op mount operation
 * @param message message to show to user
 * @param default_user preffered user
 * @param default_domain domain name
 * @param flags what type of information is required
 * @param user_data nsIChannel
 */
static void
mount_operation_ask_password (GMountOperation   *mount_op,
                              const char        *message,
                              const char        *default_user,
                              const char        *default_domain,
                              GAskPasswordFlags flags,
                              gpointer          user_data)
{
  nsIChannel *channel = (nsIChannel *) user_data;
  if (!channel) {
    g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
    return;
  }
  // We can't handle request for domain
  if (flags & G_ASK_PASSWORD_NEED_DOMAIN) {
    g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
    return;
  }

  nsCOMPtr<nsIAuthPrompt> prompt;
  NS_QueryNotificationCallbacks(channel, prompt);

  // If no auth prompt, then give up.  We could failover to using the
  // WindowWatcher service, but that might defeat a consumer's purposeful
  // attempt to disable authentication (for whatever reason).
  if (!prompt) {
    g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
    return;
  }
  // Parse out the host and port...
  nsCOMPtr<nsIURI> uri;
  channel->GetURI(getter_AddRefs(uri));
  if (!uri) {
    g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
    return;
  }

  nsAutoCString scheme, hostPort;
  uri->GetScheme(scheme);
  uri->GetHostPort(hostPort);

  // It doesn't make sense for either of these strings to be empty.  What kind
  // of funky URI is this?
  if (scheme.IsEmpty() || hostPort.IsEmpty()) {
    g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
    return;
  }
  // Construct the single signon key.  Altering the value of this key will
  // cause people's remembered passwords to be forgotten.  Think carefully
  // before changing the way this key is constructed.
  nsAutoString key, realm;

  NS_ConvertUTF8toUTF16 dispHost(scheme);
  dispHost.AppendLiteral("://");
  dispHost.Append(NS_ConvertUTF8toUTF16(hostPort));

  key = dispHost;
  if (*default_domain != '\0')
  {
    // We assume the realm string is ASCII.  That might be a bogus assumption,
    // but we have no idea what encoding GnomeVFS is using, so for now we'll
    // limit ourselves to ISO-Latin-1.  XXX What is a better solution?
    realm.Append('"');
    realm.Append(NS_ConvertASCIItoUTF16(default_domain));
    realm.Append('"');
    key.Append(' ');
    key.Append(realm);
  }
  // Construct the message string...
  //
  // We use Necko's string bundle here.  This code really should be encapsulated
  // behind some Necko API, after all this code is based closely on the code in
  // nsHttpChannel.cpp.
  nsCOMPtr<nsIStringBundleService> bundleSvc =
      do_GetService(NS_STRINGBUNDLE_CONTRACTID);
  if (!bundleSvc) {
    g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
    return;
  }
  nsCOMPtr<nsIStringBundle> bundle;
  bundleSvc->CreateBundle("chrome://global/locale/commonDialogs.properties",
                          getter_AddRefs(bundle));
  if (!bundle) {
    g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
    return;
  }
  nsAutoString nsmessage;

  if (flags & G_ASK_PASSWORD_NEED_PASSWORD) {
    if (flags & G_ASK_PASSWORD_NEED_USERNAME) {
      if (!realm.IsEmpty()) {
        const char16_t *strings[] = { realm.get(), dispHost.get() };
        bundle->FormatStringFromName(MOZ_UTF16("EnterLoginForRealm2"),
                                     strings, 2, getter_Copies(nsmessage));
      } else {
        const char16_t *strings[] = { dispHost.get() };
        bundle->FormatStringFromName(MOZ_UTF16("EnterUserPasswordFor2"),
                                     strings, 1, getter_Copies(nsmessage));
      }
    } else {
      NS_ConvertUTF8toUTF16 userName(default_user);
      const char16_t *strings[] = { userName.get(), dispHost.get() };
      bundle->FormatStringFromName(MOZ_UTF16("EnterPasswordFor"),
                                   strings, 2, getter_Copies(nsmessage));
    }
  } else {
    g_warning("Unknown mount operation request (flags: %x)", flags);
  }

  if (nsmessage.IsEmpty()) {
    g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
    return;
  }
  // Prompt the user...
  nsresult rv;
  bool retval = false;
  char16_t *user = nullptr, *pass = nullptr;
  if (default_user) {
    // user will be freed by PromptUsernameAndPassword
    user = ToNewUnicode(NS_ConvertUTF8toUTF16(default_user));
  }
  if (flags & G_ASK_PASSWORD_NEED_USERNAME) {
    rv = prompt->PromptUsernameAndPassword(nullptr, nsmessage.get(),
                                           key.get(),
                                           nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
                                           &user, &pass, &retval);
  } else {
    rv = prompt->PromptPassword(nullptr, nsmessage.get(),
                                key.get(),
                                nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
                                &pass, &retval);
  }
  if (NS_FAILED(rv) || !retval) {  //  was || user == '\0' || pass == '\0'
    g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_ABORTED);
    free(user);
    free(pass);
    return;
  }
  /* GIO should accept UTF8 */
  g_mount_operation_set_username(mount_op, NS_ConvertUTF16toUTF8(user).get());
  g_mount_operation_set_password(mount_op, NS_ConvertUTF16toUTF8(pass).get());
  free(user);
  free(pass);
  g_mount_operation_reply(mount_op, G_MOUNT_OPERATION_HANDLED);
}
static void
ProxiedAuthCallback(gconstpointer in,
                    gsize         in_size,
                    gpointer      out,
                    gsize         out_size,
                    gpointer      callback_data)
{
  GnomeVFSModuleCallbackAuthenticationIn *authIn =
      (GnomeVFSModuleCallbackAuthenticationIn *) in;
  GnomeVFSModuleCallbackAuthenticationOut *authOut =
      (GnomeVFSModuleCallbackAuthenticationOut *) out;

  LOG(("gnomevfs: ProxiedAuthCallback [uri=%s]\n", authIn->uri));

  // Without a channel, we have no way of getting a prompter.
  nsIChannel *channel = (nsIChannel *) callback_data;
  if (!channel)
    return;

  nsCOMPtr<nsIAuthPrompt> prompt;
  NS_QueryNotificationCallbacks(channel, prompt);

  // If no auth prompt, then give up.  We could failover to using the
  // WindowWatcher service, but that might defeat a consumer's purposeful
  // attempt to disable authentication (for whatever reason).
  if (!prompt)
    return;

  // Parse out the host and port...
  nsCOMPtr<nsIURI> uri;
  channel->GetURI(getter_AddRefs(uri));
  if (!uri)
    return;

#ifdef DEBUG
  {
    //
    // Make sure authIn->uri is consistent with the channel's URI.
    //
    // XXX This check is probably not IDN safe, and it might incorrectly
    //     fire as a result of escaping differences.  It's unclear what
    //     kind of transforms GnomeVFS might have applied to the URI spec
    //     that we originally gave to it.  In spite of the likelihood of
    //     false hits, this check is probably still valuable.
    //
    nsAutoCString spec;
    uri->GetSpec(spec);
    int uriLen = strlen(authIn->uri);
    if (!StringHead(spec, uriLen).Equals(nsDependentCString(authIn->uri, uriLen)))
    {
      LOG(("gnomevfs: [spec=%s authIn->uri=%s]\n", spec.get(), authIn->uri));
      NS_ERROR("URI mismatch");
    }
  }
#endif

  nsAutoCString scheme, hostPort;
  uri->GetScheme(scheme);
  uri->GetHostPort(hostPort);

  // It doesn't make sense for either of these strings to be empty.  What kind
  // of funky URI is this?
  if (scheme.IsEmpty() || hostPort.IsEmpty())
    return;

  // Construct the single signon key.  Altering the value of this key will
  // cause people's remembered passwords to be forgotten.  Think carefully
  // before changing the way this key is constructed.
  nsAutoString key, realm;

  NS_ConvertUTF8toUTF16 dispHost(scheme);
  dispHost.AppendLiteral("://");
  dispHost.Append(NS_ConvertUTF8toUTF16(hostPort));

  key = dispHost;
  if (authIn->realm)
  {
    // We assume the realm string is ASCII.  That might be a bogus assumption,
    // but we have no idea what encoding GnomeVFS is using, so for now we'll
    // limit ourselves to ISO-Latin-1.  XXX What is a better solution?
    realm.Append('"');
    realm.Append(NS_ConvertASCIItoUTF16(authIn->realm));
    realm.Append('"');
    key.Append(' ');
    key.Append(realm);
  }

  // Construct the message string...
  //
  // We use Necko's string bundle here.  This code really should be encapsulated
  // behind some Necko API, after all this code is based closely on the code in
  // nsHttpChannel.cpp.

  nsCOMPtr<nsIStringBundleService> bundleSvc =
      do_GetService(NS_STRINGBUNDLE_CONTRACTID);
  if (!bundleSvc)
    return;

  nsCOMPtr<nsIStringBundle> bundle;
  bundleSvc->CreateBundle("chrome://global/locale/commonDialogs.properties",
                          getter_AddRefs(bundle));
  if (!bundle)
    return;

  nsString message;
  if (!realm.IsEmpty())
  {
    const char16_t *strings[] = { realm.get(), dispHost.get() };
    bundle->FormatStringFromName(MOZ_UTF16("EnterUserPasswordForRealm"),
                                 strings, 2, getter_Copies(message));
  }
  else
  {
    const char16_t *strings[] = { dispHost.get() };
    bundle->FormatStringFromName(MOZ_UTF16("EnterUserPasswordFor"),
                                 strings, 1, getter_Copies(message));
  }
  if (message.IsEmpty())
    return;

  // Prompt the user...
  nsresult rv;
  bool retval = false;
  char16_t *user = nullptr, *pass = nullptr;

  rv = prompt->PromptUsernameAndPassword(nullptr, message.get(),
                                         key.get(),
                                         nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
                                         &user, &pass, &retval);
  if (NS_FAILED(rv))
    return;
  if (!retval || !user || !pass)
    return;

  // XXX We need to convert the UTF-16 username and password from our dialog to
  // strings that GnomeVFS can understand.  It's unclear what encoding GnomeVFS
  // expects, so for now we assume 7-bit ASCII.  Hopefully, we can get a better
  // solution at some point.

  // One copy is never enough...
  authOut->username = g_strdup(NS_LossyConvertUTF16toASCII(user).get());
  authOut->password = g_strdup(NS_LossyConvertUTF16toASCII(pass).get());

  nsMemory::Free(user);
  nsMemory::Free(pass);
}