/* Any copyright is dedicated to the public domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

// Test that auth prompt works.
"use strict";

SimpleTest.waitForExplicitFinish();
browserElementTestHelpers.setEnabledPref(true);
browserElementTestHelpers.addPermission();

const { NetUtil } = SpecialPowers.Cu.import('resource://gre/modules/NetUtil.jsm');

function testFail(msg) {
  ok(false, JSON.stringify(msg));
}

var iframe;

function runTest() {
  iframe = document.createElement('iframe');
  iframe.setAttribute('mozbrowser', 'true');
  document.body.appendChild(iframe);

  // Wait for the initial load to finish, then navigate the page, then start test
  // by loading SJS with http 401 response.
  iframe.addEventListener('mozbrowserloadend', function loadend() {
    iframe.removeEventListener('mozbrowserloadend', loadend);
    iframe.addEventListener('mozbrowserusernameandpasswordrequired', testHttpAuthCancel);
    SimpleTest.executeSoon(function() {
      // Use absolute path because we need to specify host.
      iframe.src = 'http://test/tests/dom/browser-element/mochitest/file_http_401_response.sjs';
    });
  });
}

function testHttpAuthCancel(e) {
  iframe.removeEventListener("mozbrowserusernameandpasswordrequired", testHttpAuthCancel);
  // Will cancel authentication, but prompt should not be shown again. Instead,
  // we will be led to fail message
  iframe.addEventListener("mozbrowserusernameandpasswordrequired", testFail);
  iframe.addEventListener("mozbrowsertitlechange", function onTitleChange(e) {
    iframe.removeEventListener("mozbrowsertitlechange", onTitleChange);
    iframe.removeEventListener("mozbrowserusernameandpasswordrequired", testFail);
    is(e.detail, 'http auth failed', 'expected authentication to fail');
    iframe.addEventListener('mozbrowserusernameandpasswordrequired', testHttpAuth);
    SimpleTest.executeSoon(function() {
      // Use absolute path because we need to specify host.
      iframe.src = 'http://test/tests/dom/browser-element/mochitest/file_http_401_response.sjs';
    });
  });

  is(e.detail.realm, 'http_realm', 'expected realm matches');
  is(e.detail.host, 'http://test', 'expected host matches');
  is(e.detail.path,
     '/tests/dom/browser-element/mochitest/file_http_401_response.sjs',
     'expected path matches');
  e.preventDefault();

  SimpleTest.executeSoon(function() {
    e.detail.cancel();
  });
}

function testHttpAuth(e) {
  iframe.removeEventListener("mozbrowserusernameandpasswordrequired", testHttpAuth);

  // Will authenticate with correct password, prompt should not be
  // called again.
  iframe.addEventListener("mozbrowserusernameandpasswordrequired", testFail);
  iframe.addEventListener("mozbrowsertitlechange", function onTitleChange(e) {
    iframe.removeEventListener("mozbrowsertitlechange", onTitleChange);
    iframe.removeEventListener("mozbrowserusernameandpasswordrequired", testFail);
    is(e.detail, 'http auth success', 'expect authentication to succeed');
    SimpleTest.executeSoon(testProxyAuth);
  });

  is(e.detail.realm, 'http_realm', 'expected realm matches');
  is(e.detail.host, 'http://test', 'expected host matches');
  is(e.detail.path,
     '/tests/dom/browser-element/mochitest/file_http_401_response.sjs',
     'expected path matches');
  is(e.detail.isProxy, false, 'expected isProxy is false');
  e.preventDefault();

  SimpleTest.executeSoon(function() {
    e.detail.authenticate("httpuser", "httppass");
  });
}

function testProxyAuth(e) {
  // The testingSJS simulates the 407 proxy authentication required response
  // for proxy server, which will trigger the browser element to send prompt
  // event with proxy infomation.
  var testingSJS = 'http://test/tests/dom/browser-element/mochitest/file_http_407_response.sjs';
  var mozproxy;

  function onUserNameAndPasswordRequired(e) {
    iframe.removeEventListener("mozbrowserusernameandpasswordrequired",
                               onUserNameAndPasswordRequired);
    iframe.addEventListener("mozbrowsertitlechange", function onTitleChange(e) {
      iframe.removeEventListener("mozbrowsertitlechange", onTitleChange);
      iframe.removeEventListener("mozbrowserusernameandpasswordrequired", testFail);
      is(e.detail, 'http auth success', 'expect authentication to succeed');
      SimpleTest.executeSoon(testAuthJarNoInterfere);
    });

    is(e.detail.realm, 'http_realm', 'expected realm matches');
    is(e.detail.host, mozproxy, 'expected host matches');
    is(e.detail.path,
       '/tests/dom/browser-element/mochitest/file_http_407_response.sjs',
       'expected path matches');
    is(e.detail.isProxy, true, 'expected isProxy is true');
    e.preventDefault();

    SimpleTest.executeSoon(function() {
      e.detail.authenticate("proxyuser", "proxypass");
    });
  }

  // Resolve proxy information used by the test suite, we need it to validate
  // whether the proxy information delivered with the prompt event is correct.
  var resolveCallback = SpecialPowers.wrapCallbackObject({
    QueryInterface: function (iid) {
      const interfaces = [Ci.nsIProtocolProxyCallback, Ci.nsISupports];

      if (!interfaces.some( function(v) { return iid.equals(v) } )) {
        throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
      }
      return this;
    },

    onProxyAvailable: function (req, channel, pi, status) {
      isnot(pi, null, 'expected proxy information available');
      if (pi) {
        mozproxy = "moz-proxy://" + pi.host + ":" + pi.port;
      }
      iframe.addEventListener("mozbrowserusernameandpasswordrequired",
                              onUserNameAndPasswordRequired);

      iframe.src = testingSJS;
    }
  });

  var channel = NetUtil.newChannel({
    uri: testingSJS,
    loadUsingSystemPrincipal: true
  });

  var pps = SpecialPowers.Cc["@mozilla.org/network/protocol-proxy-service;1"]
            .getService();

  pps.asyncResolve(channel, 0, resolveCallback);
}

function testAuthJarNoInterfere(e) {
  var authMgr = SpecialPowers.Cc['@mozilla.org/network/http-auth-manager;1']
    .getService(SpecialPowers.Ci.nsIHttpAuthManager);
  var secMan = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
               .getService(SpecialPowers.Ci.nsIScriptSecurityManager);
  var ioService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
                  .getService(SpecialPowers.Ci.nsIIOService);
  var uri = ioService.newURI("http://test/tests/dom/browser-element/mochitest/file_http_401_response.sjs", null, null);

  // Set a bunch of auth data that should not conflict with the correct auth data already
  // stored in the cache.
  var attrs = {appId: 1};
  var principal = secMan.createCodebasePrincipal(uri, attrs);
  authMgr.setAuthIdentity('http', 'test', -1, 'basic', 'http_realm',
                          'tests/dom/browser-element/mochitest/file_http_401_response.sjs',
                          '', 'httpuser', 'wrongpass', false, principal);
  attrs = {appId: 1, inIsolatedMozBrowser: true};
  principal = secMan.createCodebasePrincipal(uri, attrs);
  authMgr.setAuthIdentity('http', 'test', -1, 'basic', 'http_realm',
                          'tests/dom/browser-element/mochitest/file_http_401_response.sjs',
                          '', 'httpuser', 'wrongpass', false, principal);
  principal = secMan.createCodebasePrincipal(uri, {});
  authMgr.setAuthIdentity('http', 'test', -1, 'basic', 'http_realm',
                          'tests/dom/browser-element/mochitest/file_http_401_response.sjs',
                          '', 'httpuser', 'wrongpass', false, principal);

  // Will authenticate with correct password, prompt should not be
  // called again.
  iframe.addEventListener("mozbrowserusernameandpasswordrequired", testFail);
  iframe.addEventListener("mozbrowsertitlechange", function onTitleChange(e) {
    iframe.removeEventListener("mozbrowsertitlechange", onTitleChange);
    iframe.removeEventListener("mozbrowserusernameandpasswordrequired", testFail);
    is(e.detail, 'http auth success', 'expected authentication success');
    SimpleTest.executeSoon(testAuthJarInterfere);
  });

  // Once more with feeling. Ensure that our new auth data doesn't interfere with this mozbrowser's
  // auth data.
  iframe.src = 'http://test/tests/dom/browser-element/mochitest/file_http_401_response.sjs';
}

function testAuthJarInterfere(e) {
  var authMgr = SpecialPowers.Cc['@mozilla.org/network/http-auth-manager;1']
    .getService(SpecialPowers.Ci.nsIHttpAuthManager);
  var secMan = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
               .getService(SpecialPowers.Ci.nsIScriptSecurityManager);
  var ioService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
                  .getService(SpecialPowers.Ci.nsIIOService);
  var uri = ioService.newURI("http://test/tests/dom/browser-element/mochitest/file_http_401_response.sjs", null, null);

  // Set some auth data that should overwrite the successful stored details.
  var principal = secMan.createCodebasePrincipal(uri, {inIsolatedMozBrowser: true});
  authMgr.setAuthIdentity('http', 'test', -1, 'basic', 'http_realm',
                          'tests/dom/browser-element/mochitest/file_http_401_response.sjs',
                          '', 'httpuser', 'wrongpass', false, principal);

  // Will authenticate with correct password, prompt should not be
  // called again.
  var gotusernamepasswordrequired = false;
  function onUserNameAndPasswordRequired() {
      gotusernamepasswordrequired = true;
  }
  iframe.addEventListener("mozbrowserusernameandpasswordrequired",
                          onUserNameAndPasswordRequired);
  iframe.addEventListener("mozbrowsertitlechange", function onTitleChange(e) {
    iframe.removeEventListener("mozbrowsertitlechange", onTitleChange);
    iframe.removeEventListener("mozbrowserusernameandpasswordrequired",
                               onUserNameAndPasswordRequired);
    ok(gotusernamepasswordrequired,
       "Should have dispatched mozbrowserusernameandpasswordrequired event");
    testFinish();
  });

  // Once more with feeling. Ensure that our new auth data interferes with this mozbrowser's
  // auth data.
  iframe.src = 'http://test/tests/dom/browser-element/mochitest/file_http_401_response.sjs';
}

function testFinish() {
  // Clear login information stored in password manager.
  var authMgr = SpecialPowers.Cc['@mozilla.org/network/http-auth-manager;1']
    .getService(SpecialPowers.Ci.nsIHttpAuthManager);
  authMgr.clearAll();

  var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
    .getService(SpecialPowers.Ci.nsILoginManager);
  pwmgr.removeAllLogins();

  SimpleTest.finish();
}

addEventListener('testready', runTest);