/* 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/. */

"use strict";

/* import-globals-from head_cache.js */
/* import-globals-from head_cookies.js */
/* import-globals-from head_channels.js */
/* import-globals-from head_servers.js */

function makeChan(uri, loadingUrl) {
  let principal = Services.scriptSecurityManager.createContentPrincipal(
    Services.io.newURI(loadingUrl),
    {}
  );
  return NetUtil.newChannel({
    uri,
    loadingPrincipal: principal,
    securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
    contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
  });
}

class AuthPrompt {
  constructor() {
    this.QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt2"]);
  }
  asyncPromptAuth(channel, callback, context, encryptionLevel, authInfo) {
    executeSoon(function () {
      authInfo.username = "guest";
      authInfo.password = "guest";
      callback.onAuthAvailable(context, authInfo);
    });
  }
}

class AuthRequestor {
  constructor(prompt) {
    this.prompt = prompt;
    this.QueryInterface = ChromeUtils.generateQI(["nsIInterfaceRequestor"]);
  }
  getInterface(iid) {
    if (iid.equals(Ci.nsIAuthPrompt2)) {
      return this.prompt();
    }
    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
  }
}

/**
 * Verify HTTP/2 auth retry behavior: the server issues two 401 challenges
 * and only returns 200 OK on the third request.
 *
 * This test ensures the channel performs two auth retries and succeeds on the
 * third attempt.
 *
 */
add_task(async function test_http2_auth_retry_twice() {
  Services.prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);

  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    Ci.nsIX509CertDB
  );
  addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");

  let server = new NodeHTTP2Server();
  await server.start();
  registerCleanupFunction(async () => {
    await server.stop();
  });

  await server.registerPathHandler("/test", (req, res) => {
    const hasAuth =
      typeof req.headers.authorization === "string" &&
      !!req.headers.authorization.length;
    global.count ??= 0;
    global.count++;
    if (!hasAuth || global.count < 3) {
      res.stream.respond({
        ":status": 401,
        "content-type": "text/plain; charset=utf-8",
        "www-authenticate": 'Basic realm="secret"',
      });
      res.end("Unauthorized\n");
      return;
    }

    res.stream.respond({
      ":status": 200,
      "content-type": "text/plain; charset=utf-8",
    });
    res.end("OK\n");
  });

  let chan = makeChan(
    `https://localhost:${server.port()}/test`,
    `https://localhost:${server.port()}`
  );
  chan.notificationCallbacks = new AuthRequestor(() => new AuthPrompt());

  let req = await new Promise(resolve => {
    chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
  });
  equal(req.status, Cr.NS_OK);
  equal(req.QueryInterface(Ci.nsIHttpChannel).responseStatus, 200);
  equal(req.QueryInterface(Ci.nsIHttpChannel).protocolVersion, "h2");
});
