tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_authentication.js (38913B)


      1 // This file tests authentication prompt callbacks
      2 // TODO NIT use do_check_eq(expected, actual) consistently, not sometimes eq(actual, expected)
      3 
      4 "use strict";
      5 
      6 const { HttpServer } = ChromeUtils.importESModule(
      7  "resource://testing-common/httpd.sys.mjs"
      8 );
      9 
     10 // Turn off the authentication dialog blocking for this test.
     11 Services.prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
     12 
     13 ChromeUtils.defineLazyGetter(this, "URL", function () {
     14  return "http://localhost:" + httpserv.identity.primaryPort;
     15 });
     16 
     17 ChromeUtils.defineLazyGetter(this, "PORT", function () {
     18  return httpserv.identity.primaryPort;
     19 });
     20 
     21 const FLAG_RETURN_FALSE = 1 << 0;
     22 const FLAG_WRONG_PASSWORD = 1 << 1;
     23 const FLAG_BOGUS_USER = 1 << 2;
     24 const FLAG_PREVIOUS_FAILED = 1 << 3;
     25 const CROSS_ORIGIN = 1 << 4;
     26 const FLAG_NO_REALM = 1 << 5;
     27 const FLAG_NON_ASCII_USER_PASSWORD = 1 << 6;
     28 
     29 const nsIAuthPrompt2 = Ci.nsIAuthPrompt2;
     30 const nsIAuthInformation = Ci.nsIAuthInformation;
     31 
     32 function AuthPrompt1(flags) {
     33  this.flags = flags;
     34 }
     35 
     36 var initialChannelId = -1;
     37 
     38 AuthPrompt1.prototype = {
     39  user: "guest",
     40  pass: "guest",
     41 
     42  expectedRealm: "secret",
     43 
     44  QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt"]),
     45 
     46  prompt: function ap1_prompt() {
     47    do_throw("unexpected prompt call");
     48  },
     49 
     50  promptUsernameAndPassword: function ap1_promptUP(
     51    title,
     52    text,
     53    realm,
     54    savePW,
     55    user,
     56    pw
     57  ) {
     58    if (this.flags & FLAG_NO_REALM) {
     59      // Note that the realm here isn't actually the realm. it's a pw mgr key.
     60      Assert.equal(URL + " (" + this.expectedRealm + ")", realm);
     61    }
     62    if (!(this.flags & CROSS_ORIGIN)) {
     63      if (!text.includes(this.expectedRealm)) {
     64        do_throw("Text must indicate the realm");
     65      }
     66    } else if (text.includes(this.expectedRealm)) {
     67      do_throw("There should not be realm for cross origin");
     68    }
     69    if (!text.includes("localhost")) {
     70      do_throw("Text must indicate the hostname");
     71    }
     72    if (!text.includes(String(PORT))) {
     73      do_throw("Text must indicate the port");
     74    }
     75    if (text.includes("-1")) {
     76      do_throw("Text must contain negative numbers");
     77    }
     78 
     79    if (this.flags & FLAG_RETURN_FALSE) {
     80      return false;
     81    }
     82 
     83    if (this.flags & FLAG_BOGUS_USER) {
     84      this.user = "foo\nbar";
     85    } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
     86      this.user = "é";
     87    }
     88 
     89    user.value = this.user;
     90    if (this.flags & FLAG_WRONG_PASSWORD) {
     91      pw.value = this.pass + ".wrong";
     92      // Now clear the flag to avoid an infinite loop
     93      this.flags &= ~FLAG_WRONG_PASSWORD;
     94    } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
     95      pw.value = "é";
     96    } else {
     97      pw.value = this.pass;
     98    }
     99    return true;
    100  },
    101 
    102  promptPassword: function ap1_promptPW() {
    103    do_throw("unexpected promptPassword call");
    104  },
    105 };
    106 
    107 function AuthPrompt2(flags) {
    108  this.flags = flags;
    109 }
    110 
    111 AuthPrompt2.prototype = {
    112  user: "guest",
    113  pass: "guest",
    114 
    115  expectedRealm: "secret",
    116 
    117  QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
    118 
    119  promptAuth: function ap2_promptAuth(channel, level, authInfo) {
    120    var isNTLM = channel.URI.pathQueryRef.includes("ntlm");
    121    var isDigest = channel.URI.pathQueryRef.includes("digest");
    122 
    123    if (isNTLM || this.flags & FLAG_NO_REALM) {
    124      this.expectedRealm = ""; // NTLM knows no realms
    125    }
    126 
    127    Assert.equal(this.expectedRealm, authInfo.realm);
    128 
    129    var expectedLevel =
    130      isNTLM || isDigest
    131        ? nsIAuthPrompt2.LEVEL_PW_ENCRYPTED
    132        : nsIAuthPrompt2.LEVEL_NONE;
    133    Assert.equal(expectedLevel, level);
    134 
    135    var expectedFlags = nsIAuthInformation.AUTH_HOST;
    136 
    137    if (this.flags & FLAG_PREVIOUS_FAILED) {
    138      expectedFlags |= nsIAuthInformation.PREVIOUS_FAILED;
    139    }
    140 
    141    if (this.flags & CROSS_ORIGIN) {
    142      expectedFlags |= nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE;
    143    }
    144 
    145    if (isNTLM) {
    146      expectedFlags |= nsIAuthInformation.NEED_DOMAIN;
    147    }
    148 
    149    const kAllKnownFlags = 127; // Don't fail test for newly added flags
    150    Assert.equal(expectedFlags, authInfo.flags & kAllKnownFlags);
    151 
    152    // eslint-disable-next-line no-nested-ternary
    153    var expectedScheme = isNTLM ? "ntlm" : isDigest ? "digest" : "basic";
    154    Assert.equal(expectedScheme, authInfo.authenticationScheme);
    155 
    156    // No passwords in the URL -> nothing should be prefilled
    157    Assert.equal(authInfo.username, "");
    158    Assert.equal(authInfo.password, "");
    159    Assert.equal(authInfo.domain, "");
    160 
    161    if (this.flags & FLAG_RETURN_FALSE) {
    162      this.flags |= FLAG_PREVIOUS_FAILED;
    163      return false;
    164    }
    165 
    166    if (this.flags & FLAG_BOGUS_USER) {
    167      this.user = "foo\nbar";
    168    } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
    169      this.user = "é";
    170    }
    171 
    172    authInfo.username = this.user;
    173    if (this.flags & FLAG_WRONG_PASSWORD) {
    174      authInfo.password = this.pass + ".wrong";
    175      this.flags |= FLAG_PREVIOUS_FAILED;
    176      // Now clear the flag to avoid an infinite loop
    177      this.flags &= ~FLAG_WRONG_PASSWORD;
    178    } else if (this.flags & FLAG_NON_ASCII_USER_PASSWORD) {
    179      authInfo.password = "é";
    180    } else {
    181      authInfo.password = this.pass;
    182      this.flags &= ~FLAG_PREVIOUS_FAILED;
    183    }
    184    return true;
    185  },
    186 
    187  asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) {
    188    let self = this;
    189    executeSoon(function () {
    190      let ret = self.promptAuth(chan, lvl, info);
    191      if (ret) {
    192        cb.onAuthAvailable(ctx, info);
    193      } else {
    194        cb.onAuthCancelled(ctx, true);
    195      }
    196    });
    197  },
    198 };
    199 
    200 function Requestor(flags, versions) {
    201  this.flags = flags;
    202  this.versions = versions;
    203 }
    204 
    205 Requestor.prototype = {
    206  QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
    207 
    208  getInterface: function requestor_gi(iid) {
    209    if (this.versions & 1 && iid.equals(Ci.nsIAuthPrompt)) {
    210      // Allow the prompt to store state by caching it here
    211      if (!this.prompt1) {
    212        this.prompt1 = new AuthPrompt1(this.flags);
    213      }
    214      return this.prompt1;
    215    }
    216    if (this.versions & 2 && iid.equals(Ci.nsIAuthPrompt2)) {
    217      // Allow the prompt to store state by caching it here
    218      if (!this.prompt2) {
    219        this.prompt2 = new AuthPrompt2(this.flags);
    220      }
    221      return this.prompt2;
    222    }
    223 
    224    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
    225  },
    226 
    227  prompt1: null,
    228  prompt2: null,
    229 };
    230 
    231 function RealmTestRequestor() {
    232  this.promptRealm = "";
    233 }
    234 
    235 RealmTestRequestor.prototype = {
    236  QueryInterface: ChromeUtils.generateQI([
    237    "nsIInterfaceRequestor",
    238    "nsIAuthPrompt2",
    239  ]),
    240 
    241  getInterface: function realmtest_interface(iid) {
    242    if (iid.equals(Ci.nsIAuthPrompt2)) {
    243      return this;
    244    }
    245 
    246    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
    247  },
    248 
    249  promptAuth: function realmtest_checkAuth(channel, level, authInfo) {
    250    this.promptRealm = authInfo.realm;
    251 
    252    return false;
    253  },
    254 
    255  asyncPromptAuth: function realmtest_async() {
    256    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
    257  },
    258 };
    259 
    260 var listener = {
    261  expectedCode: -1, // Uninitialized
    262  nextTest: undefined,
    263  expectRequestFail: false,
    264  onStartRequest: function test_onStartR(request) {
    265    try {
    266      if (
    267        !this.expectRequestFail &&
    268        !Components.isSuccessCode(request.status)
    269      ) {
    270        do_throw("Channel should have a success code!");
    271      }
    272 
    273      if (!(request instanceof Ci.nsIHttpChannel)) {
    274        do_throw("Expecting an HTTP channel");
    275      }
    276 
    277      if (
    278        Services.prefs.getBoolPref("network.auth.use_redirect_for_retries") &&
    279        // we should skip redirect check if we do not expect to succeed
    280        this.expectedCode == 200
    281      ) {
    282        // ensure channel ids are initialized
    283        Assert.notEqual(initialChannelId, -1);
    284 
    285        // for each request we must use a unique channel ID.
    286        // See Bug 1820807
    287        var chan = request.QueryInterface(Ci.nsIIdentChannel);
    288        Assert.notEqual(initialChannelId, chan.channelId);
    289      }
    290 
    291      Assert.equal(request.responseStatus, this.expectedCode);
    292      // The request should be succeeded if we expect 200
    293      Assert.equal(request.requestSucceeded, this.expectedCode == 200);
    294    } catch (e) {
    295      do_throw("Unexpected exception: " + e);
    296    }
    297 
    298    throw Components.Exception("", Cr.NS_ERROR_ABORT);
    299  },
    300 
    301  onDataAvailable: function test_ODA() {
    302    do_throw("Should not get any data!");
    303  },
    304 
    305  onStopRequest: function test_onStopR(request, status) {
    306    Assert.equal(status, Cr.NS_ERROR_ABORT);
    307    initialChannelId = -1;
    308    this.nextTest();
    309  },
    310 };
    311 
    312 let ChannelEventSink1 = {
    313  _classDescription: "WebRequest channel event sink",
    314  _classID: Components.ID("115062f8-92f1-11e5-8b7f-08001110f7ec"),
    315  _contractID: "@mozilla.org/webrequest/channel-event-sink;1",
    316 
    317  QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink", "nsIFactory"]),
    318 
    319  init() {
    320    Components.manager
    321      .QueryInterface(Ci.nsIComponentRegistrar)
    322      .registerFactory(
    323        this._classID,
    324        this._classDescription,
    325        this._contractID,
    326        this
    327      );
    328  },
    329 
    330  register() {
    331    Services.catMan.addCategoryEntry(
    332      "net-channel-event-sinks",
    333      this._contractID,
    334      this._contractID,
    335      false,
    336      true
    337    );
    338  },
    339 
    340  unregister() {
    341    Services.catMan.deleteCategoryEntry(
    342      "net-channel-event-sinks",
    343      this._contractID,
    344      false
    345    );
    346  },
    347 
    348  // nsIChannelEventSink implementation
    349  asyncOnChannelRedirect(oldChannel, newChannel, flags, redirectCallback) {
    350    // Abort the redirection
    351    redirectCallback.onRedirectVerifyCallback(Cr.NS_ERROR_ABORT);
    352  },
    353 
    354  // nsIFactory implementation
    355  createInstance(iid) {
    356    return this.QueryInterface(iid);
    357  },
    358 };
    359 
    360 function makeChan(
    361  url,
    362  loadingUrl,
    363  securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
    364  contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER
    365 ) {
    366  var principal = Services.scriptSecurityManager.createContentPrincipal(
    367    Services.io.newURI(loadingUrl),
    368    {}
    369  );
    370  return NetUtil.newChannel({
    371    uri: url,
    372    loadingPrincipal: principal,
    373    securityFlags,
    374    contentPolicyType,
    375  });
    376 }
    377 
    378 var ChannelCreationObserver = {
    379  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
    380  observe(aSubject, aTopic) {
    381    if (aTopic == "http-on-opening-request") {
    382      initialChannelId = aSubject.QueryInterface(Ci.nsIIdentChannel).channelId;
    383    }
    384  },
    385 };
    386 
    387 var httpserv = null;
    388 
    389 function setup() {
    390  httpserv = new HttpServer();
    391 
    392  httpserv.registerPathHandler("/auth", authHandler);
    393  httpserv.registerPathHandler(
    394    "/auth/stored/wrong/credentials/",
    395    authHandlerWrongStoredCredentials
    396  );
    397  httpserv.registerPathHandler("/auth/ntlm/simple", authNtlmSimple);
    398  httpserv.registerPathHandler("/auth/realm", authRealm);
    399  httpserv.registerPathHandler("/auth/non_ascii", authNonascii);
    400  httpserv.registerPathHandler("/auth/digest_md5", authDigestMD5);
    401  httpserv.registerPathHandler("/auth/digest_md5sess", authDigestMD5sess);
    402  httpserv.registerPathHandler("/auth/digest_sha256", authDigestSHA256);
    403  httpserv.registerPathHandler("/auth/digest_sha256sess", authDigestSHA256sess);
    404  httpserv.registerPathHandler("/auth/digest_sha256_md5", authDigestSHA256_MD5);
    405  httpserv.registerPathHandler("/auth/digest_md5_sha256", authDigestMD5_SHA256);
    406  httpserv.registerPathHandler(
    407    "/auth/digest_md5_sha256_oneline",
    408    authDigestMD5_SHA256_oneline
    409  );
    410  httpserv.registerPathHandler("/auth/short_digest", authShortDigest);
    411  httpserv.registerPathHandler("/largeRealm", largeRealm);
    412  httpserv.registerPathHandler("/largeDomain", largeDomain);
    413 
    414  httpserv.registerPathHandler("/corp-coep", corpAndCoep);
    415 
    416  httpserv.start(-1);
    417 
    418  registerCleanupFunction(async () => {
    419    await httpserv.stop();
    420  });
    421  Services.obs.addObserver(ChannelCreationObserver, "http-on-opening-request");
    422 }
    423 setup();
    424 
    425 async function openAndListen(chan) {
    426  await new Promise(resolve => {
    427    listener.nextTest = resolve;
    428    chan.asyncOpen(listener);
    429  });
    430  Cc["@mozilla.org/network/http-auth-manager;1"]
    431    .getService(Ci.nsIHttpAuthManager)
    432    .clearAll();
    433 }
    434 
    435 async function test_noauth() {
    436  var chan = makeChan(URL + "/auth", URL);
    437 
    438  listener.expectedCode = 401; // Unauthorized
    439  await openAndListen(chan);
    440 }
    441 
    442 async function test_returnfalse1() {
    443  var chan = makeChan(URL + "/auth", URL);
    444 
    445  chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 1);
    446  listener.expectedCode = 401; // Unauthorized
    447  await openAndListen(chan);
    448 }
    449 
    450 async function test_wrongpw1() {
    451  var chan = makeChan(URL + "/auth", URL);
    452 
    453  chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 1);
    454  listener.expectedCode = 200; // OK
    455  await openAndListen(chan);
    456 }
    457 
    458 async function test_prompt1() {
    459  var chan = makeChan(URL + "/auth", URL);
    460 
    461  chan.notificationCallbacks = new Requestor(0, 1);
    462  listener.expectedCode = 200; // OK
    463  await openAndListen(chan);
    464 }
    465 
    466 async function test_prompt1CrossOrigin() {
    467  var chan = makeChan(URL + "/auth", "http://example.org");
    468 
    469  chan.notificationCallbacks = new Requestor(16, 1);
    470  listener.expectedCode = 200; // OK
    471  await openAndListen(chan);
    472 }
    473 
    474 async function test_prompt2CrossOrigin() {
    475  var chan = makeChan(URL + "/auth", "http://example.org");
    476 
    477  chan.notificationCallbacks = new Requestor(16, 2);
    478  listener.expectedCode = 200; // OK
    479  await openAndListen(chan);
    480 }
    481 
    482 async function test_returnfalse2() {
    483  var chan = makeChan(URL + "/auth", URL);
    484 
    485  chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
    486  listener.expectedCode = 401; // Unauthorized
    487  await openAndListen(chan);
    488 }
    489 
    490 async function test_wrongpw2() {
    491  var chan = makeChan(URL + "/auth", URL);
    492 
    493  chan.notificationCallbacks = new Requestor(FLAG_WRONG_PASSWORD, 2);
    494  listener.expectedCode = 200; // OK
    495  await openAndListen(chan);
    496 }
    497 
    498 var requestNum = 0;
    499 var expectedRequestNum = 0;
    500 async function test_wrong_stored_passwd() {
    501  // tests that we don't retry auth requests for incorrect custom credentials passed during channel creation
    502  requestNum = 0;
    503  expectedRequestNum = 1;
    504  var chan = makeChan(URL + "/auth/stored/wrong/credentials/", URL);
    505  chan.nsIHttpChannel.setRequestHeader("Authorization", "wrong_cred", false);
    506  chan.notificationCallbacks = new Requestor(0, 1);
    507  listener.expectedCode = 401; // Unauthorized
    508 
    509  await openAndListen(chan);
    510 }
    511 
    512 async function test_prompt2() {
    513  var chan = makeChan(URL + "/auth", URL);
    514 
    515  chan.notificationCallbacks = new Requestor(0, 2);
    516  listener.expectedCode = 200; // OK
    517  await openAndListen(chan);
    518 }
    519 
    520 async function test_ntlm() {
    521  var chan = makeChan(URL + "/auth/ntlm/simple", URL);
    522 
    523  chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
    524  listener.expectedCode = 401; // Unauthorized
    525  await openAndListen(chan);
    526 }
    527 
    528 async function test_basicrealm() {
    529  var chan = makeChan(URL + "/auth/realm", URL);
    530 
    531  let requestor = new RealmTestRequestor();
    532  chan.notificationCallbacks = requestor;
    533  listener.expectedCode = 401; // Unauthorized
    534  await openAndListen(chan);
    535  Assert.equal(requestor.promptRealm, '"foo_bar');
    536 }
    537 
    538 async function test_nonascii() {
    539  var chan = makeChan(URL + "/auth/non_ascii", URL);
    540 
    541  chan.notificationCallbacks = new Requestor(FLAG_NON_ASCII_USER_PASSWORD, 2);
    542  listener.expectedCode = 200; // OK
    543  await openAndListen(chan);
    544 }
    545 
    546 async function test_digest_noauth() {
    547  var chan = makeChan(URL + "/auth/digest_md5", URL);
    548 
    549  // chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
    550  listener.expectedCode = 401; // Unauthorized
    551  await openAndListen(chan);
    552 }
    553 
    554 async function test_digest_md5() {
    555  var chan = makeChan(URL + "/auth/digest_md5", URL);
    556 
    557  chan.notificationCallbacks = new Requestor(0, 2);
    558  listener.expectedCode = 200; // OK
    559  await openAndListen(chan);
    560 }
    561 
    562 add_task(
    563  { pref_set: [["network.auth.use_redirect_for_retries", true]] },
    564  async function test_digest_md5_redirect_veto() {
    565    ChannelEventSink1.init();
    566    ChannelEventSink1.register();
    567    var chan = makeChan(URL + "/auth/digest_md5", URL);
    568 
    569    chan.notificationCallbacks = new Requestor(0, 1);
    570    listener.expectedCode = 401; // Unauthorized
    571    listener.expectRequestFail = true;
    572    await openAndListen(chan);
    573    ChannelEventSink1.unregister();
    574    listener.expectRequestFail = false;
    575  }
    576 );
    577 
    578 async function test_digest_md5sess() {
    579  var chan = makeChan(URL + "/auth/digest_md5sess", URL);
    580 
    581  chan.notificationCallbacks = new Requestor(0, 2);
    582  listener.expectedCode = 200; // OK
    583  await openAndListen(chan);
    584 }
    585 
    586 async function test_digest_sha256() {
    587  var chan = makeChan(URL + "/auth/digest_sha256", URL);
    588 
    589  chan.notificationCallbacks = new Requestor(0, 2);
    590  listener.expectedCode = 200; // OK
    591  await openAndListen(chan);
    592 }
    593 
    594 async function test_digest_sha256sess() {
    595  var chan = makeChan(URL + "/auth/digest_sha256sess", URL);
    596 
    597  chan.notificationCallbacks = new Requestor(0, 2);
    598  listener.expectedCode = 200; // OK
    599  await openAndListen(chan);
    600 }
    601 
    602 async function test_digest_sha256_md5() {
    603  var chan = makeChan(URL + "/auth/digest_sha256_md5", URL);
    604 
    605  chan.notificationCallbacks = new Requestor(0, 2);
    606  listener.expectedCode = 200; // OK
    607  await openAndListen(chan);
    608 }
    609 
    610 async function test_digest_md5_sha256() {
    611  var chan = makeChan(URL + "/auth/digest_md5_sha256", URL);
    612 
    613  chan.notificationCallbacks = new Requestor(0, 2);
    614  listener.expectedCode = 200; // OK
    615  await openAndListen(chan);
    616 }
    617 
    618 async function test_digest_md5_sha256_oneline() {
    619  var chan = makeChan(URL + "/auth/digest_md5_sha256_oneline", URL);
    620 
    621  chan.notificationCallbacks = new Requestor(0, 2);
    622  listener.expectedCode = 200; // OK
    623  await openAndListen(chan);
    624 }
    625 
    626 async function test_digest_bogus_user() {
    627  var chan = makeChan(URL + "/auth/digest_md5", URL);
    628  chan.notificationCallbacks = new Requestor(FLAG_BOGUS_USER, 2);
    629  listener.expectedCode = 401; // unauthorized
    630  await openAndListen(chan);
    631 }
    632 
    633 // Test header "WWW-Authenticate: Digest" - bug 1338876.
    634 async function test_short_digest() {
    635  var chan = makeChan(URL + "/auth/short_digest", URL);
    636  chan.notificationCallbacks = new Requestor(FLAG_NO_REALM, 2);
    637  listener.expectedCode = 401; // OK
    638  await openAndListen(chan);
    639 }
    640 
    641 // Test that COOP/COEP are processed even though asyncPromptAuth is cancelled.
    642 async function test_corp_coep() {
    643  var chan = makeChan(
    644    URL + "/corp-coep",
    645    URL,
    646    Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
    647    Ci.nsIContentPolicy.TYPE_DOCUMENT
    648  );
    649 
    650  chan.notificationCallbacks = new Requestor(FLAG_RETURN_FALSE, 2);
    651  listener.expectedCode = 401; // OK
    652  await openAndListen(chan);
    653 
    654  Assert.equal(
    655    chan.getResponseHeader("cross-origin-embedder-policy"),
    656    "require-corp"
    657  );
    658  Assert.equal(
    659    chan.getResponseHeader("cross-origin-opener-policy"),
    660    "same-origin"
    661  );
    662 }
    663 
    664 // XXX(valentin): this makes tests fail if it's not run last. Why?
    665 async function test_nonascii_xhr() {
    666  await new Promise(resolve => {
    667    let xhr = new XMLHttpRequest();
    668    xhr.open("GET", URL + "/auth/non_ascii", true, "é", "é");
    669    xhr.onreadystatechange = function () {
    670      if (xhr.readyState == 4) {
    671        Assert.equal(xhr.status, 200);
    672        resolve();
    673        xhr.onreadystatechange = null;
    674      }
    675    };
    676    xhr.send(null);
    677  });
    678 }
    679 
    680 let auth_tests = [
    681  test_noauth,
    682  test_returnfalse1,
    683  test_wrongpw1,
    684  test_wrong_stored_passwd,
    685  test_prompt1,
    686  test_prompt1CrossOrigin,
    687  test_prompt2CrossOrigin,
    688  test_returnfalse2,
    689  test_wrongpw2,
    690  test_prompt2,
    691  test_ntlm,
    692  test_basicrealm,
    693  test_nonascii,
    694  test_digest_noauth,
    695  test_digest_md5,
    696  test_digest_md5sess,
    697  test_digest_sha256,
    698  test_digest_sha256sess,
    699  test_digest_sha256_md5,
    700  test_digest_md5_sha256,
    701  test_digest_md5_sha256_oneline,
    702  test_digest_bogus_user,
    703  test_short_digest,
    704  test_corp_coep,
    705  test_nonascii_xhr,
    706 ];
    707 
    708 for (let auth_test of auth_tests) {
    709  add_task(
    710    { pref_set: [["network.auth.use_redirect_for_retries", false]] },
    711    auth_test
    712  );
    713 }
    714 
    715 for (let auth_test of auth_tests) {
    716  add_task(
    717    { pref_set: [["network.auth.use_redirect_for_retries", true]] },
    718    auth_test
    719  );
    720 }
    721 
    722 // PATH HANDLERS
    723 
    724 // /auth
    725 function authHandler(metadata, response) {
    726  // btoa("guest:guest"), but that function is not available here
    727  var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
    728 
    729  var body;
    730  if (
    731    metadata.hasHeader("Authorization") &&
    732    metadata.getHeader("Authorization") == expectedHeader
    733  ) {
    734    response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
    735    response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
    736 
    737    body = "success";
    738  } else {
    739    // didn't know guest:guest, failure
    740    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    741    response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
    742 
    743    body = "failed";
    744  }
    745 
    746  response.bodyOutputStream.write(body, body.length);
    747 }
    748 
    749 function authHandlerWrongStoredCredentials(metadata, response) {
    750  var body;
    751  if (++requestNum > expectedRequestNum) {
    752    response.setStatusLine(metadata.httpVersion, 500, "");
    753  } else {
    754    response.setStatusLine(
    755      metadata.httpVersion,
    756      401,
    757      "Unauthorized" + requestNum
    758    );
    759    response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
    760  }
    761 
    762  body = "failed";
    763  response.bodyOutputStream.write(body, body.length);
    764 }
    765 
    766 // /auth/ntlm/simple
    767 function authNtlmSimple(metadata, response) {
    768  response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    769  response.setHeader(
    770    "WWW-Authenticate",
    771    "NTLM" /* + ' realm="secret"' */,
    772    false
    773  );
    774 
    775  var body =
    776    "NOTE: This just sends an NTLM challenge, it never\n" +
    777    "accepts the authentication. It also closes\n" +
    778    "the connection after sending the challenge\n";
    779 
    780  response.bodyOutputStream.write(body, body.length);
    781 }
    782 
    783 // /auth/realm
    784 function authRealm(metadata, response) {
    785  response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    786  response.setHeader("WWW-Authenticate", 'Basic realm="\\"f\\oo_bar"', false);
    787  var body = "success";
    788 
    789  response.bodyOutputStream.write(body, body.length);
    790 }
    791 
    792 // /auth/nonAscii
    793 function authNonascii(metadata, response) {
    794  // btoa("é:é"), but that function is not available here
    795  var expectedHeader = "Basic w6k6w6k=";
    796 
    797  var body;
    798  if (
    799    metadata.hasHeader("Authorization") &&
    800    metadata.getHeader("Authorization") == expectedHeader
    801  ) {
    802    response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
    803    response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
    804 
    805    // Use correct XML syntax since this function is also used for testing XHR.
    806    body = "<?xml version='1.0' ?><root>success</root>";
    807  } else {
    808    // didn't know é:é, failure
    809    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    810    response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
    811 
    812    body = "<?xml version='1.0' ?><root>failed</root>";
    813  }
    814 
    815  response.bodyOutputStream.write(body, body.length);
    816 }
    817 
    818 function corpAndCoep(metadata, response) {
    819  response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    820  response.setHeader("cross-origin-embedder-policy", "require-corp");
    821  response.setHeader("cross-origin-opener-policy", "same-origin");
    822  response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
    823 }
    824 
    825 //
    826 // Digest functions
    827 //
    828 function bytesFromString(str) {
    829  return new TextEncoder().encode(str);
    830 }
    831 
    832 // return the two-digit hexadecimal code for a byte
    833 function toHexString(charCode) {
    834  return ("0" + charCode.toString(16)).slice(-2);
    835 }
    836 
    837 function HMD5(str) {
    838  var data = bytesFromString(str);
    839  var ch = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
    840  ch.init(Ci.nsICryptoHash.MD5);
    841  ch.update(data, data.length);
    842  var hash = ch.finish(false);
    843  return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
    844 }
    845 
    846 function HSHA256(str) {
    847  var data = bytesFromString(str);
    848  var ch = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
    849  ch.init(Ci.nsICryptoHash.SHA256);
    850  ch.update(data, data.length);
    851  var hash = ch.finish(false);
    852  return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
    853 }
    854 
    855 //
    856 // Digest handler
    857 //
    858 // /auth/digest
    859 function authDigestMD5_helper(metadata, response, test_name) {
    860  var nonce = "6f93719059cf8d568005727f3250e798";
    861  var opaque = "1234opaque1234";
    862  var body;
    863  var send_401 = 0;
    864  // check creds if we have them
    865  if (metadata.hasHeader("Authorization")) {
    866    var cnonceRE = /cnonce="(\w+)"/;
    867    var responseRE = /response="(\w+)"/;
    868    var usernameRE = /username="(\w+)"/;
    869    var algorithmRE = /algorithm=([\w-]+)/;
    870    var auth = metadata.getHeader("Authorization");
    871    var cnonce = auth.match(cnonceRE)[1];
    872    var clientDigest = auth.match(responseRE)[1];
    873    var username = auth.match(usernameRE)[1];
    874    var algorithm = auth.match(algorithmRE)[1];
    875    var nc = "00000001";
    876 
    877    if (username != "guest") {
    878      response.setStatusLine(metadata.httpVersion, 400, "bad request");
    879      body = "should never get here";
    880    } else if (
    881      algorithm != null &&
    882      algorithm != "MD5" &&
    883      algorithm != "MD5-sess"
    884    ) {
    885      response.setStatusLine(metadata.httpVersion, 400, "bad request");
    886      body = "Algorithm must be same as provided in WWW-Authenticate header";
    887    } else {
    888      // see RFC2617 for the description of this calculation
    889      var A1 = "guest:secret:guest";
    890      if (algorithm == "MD5-sess") {
    891        A1 = [HMD5(A1), nonce, cnonce].join(":");
    892      }
    893      var A2 = "GET:/auth/" + test_name;
    894      var noncebits = [nonce, nc, cnonce, "auth", HMD5(A2)].join(":");
    895      var digest = HMD5([HMD5(A1), noncebits].join(":"));
    896 
    897      if (clientDigest == digest) {
    898        response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
    899        body = "success";
    900      } else {
    901        send_401 = 1;
    902        body = "auth failed";
    903      }
    904    }
    905  } else {
    906    // no header, send one
    907    send_401 = 1;
    908    body = "failed, no header";
    909  }
    910 
    911  if (send_401) {
    912    var authenticate_md5 =
    913      'Digest realm="secret", domain="/",  qop=auth,' +
    914      'algorithm=MD5, nonce="' +
    915      nonce +
    916      '" opaque="' +
    917      opaque +
    918      '"';
    919    var authenticate_md5sess =
    920      'Digest realm="secret", domain="/",  qop=auth,' +
    921      'algorithm=MD5, nonce="' +
    922      nonce +
    923      '" opaque="' +
    924      opaque +
    925      '"';
    926    if (test_name == "digest_md5") {
    927      response.setHeader("WWW-Authenticate", authenticate_md5, false);
    928    } else if (test_name == "digest_md5sess") {
    929      response.setHeader("WWW-Authenticate", authenticate_md5sess, false);
    930    }
    931    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
    932  }
    933 
    934  response.bodyOutputStream.write(body, body.length);
    935 }
    936 
    937 function authDigestMD5(metadata, response) {
    938  authDigestMD5_helper(metadata, response, "digest_md5");
    939 }
    940 
    941 function authDigestMD5sess(metadata, response) {
    942  authDigestMD5_helper(metadata, response, "digest_md5sess");
    943 }
    944 
    945 function authDigestSHA256_helper(metadata, response, test_name) {
    946  var nonce = "6f93719059cf8d568005727f3250e798";
    947  var opaque = "1234opaque1234";
    948  var body;
    949  var send_401 = 0;
    950  // check creds if we have them
    951  if (metadata.hasHeader("Authorization")) {
    952    var cnonceRE = /cnonce="(\w+)"/;
    953    var responseRE = /response="(\w+)"/;
    954    var usernameRE = /username="(\w+)"/;
    955    var algorithmRE = /algorithm=([\w-]+)/;
    956    var auth = metadata.getHeader("Authorization");
    957    var cnonce = auth.match(cnonceRE)[1];
    958    var clientDigest = auth.match(responseRE)[1];
    959    var username = auth.match(usernameRE)[1];
    960    var algorithm = auth.match(algorithmRE)[1];
    961    var nc = "00000001";
    962 
    963    if (username != "guest") {
    964      response.setStatusLine(metadata.httpVersion, 400, "bad request");
    965      body = "should never get here";
    966    } else if (algorithm != "SHA-256" && algorithm != "SHA-256-sess") {
    967      response.setStatusLine(metadata.httpVersion, 400, "bad request");
    968      body = "Algorithm must be same as provided in WWW-Authenticate header";
    969    } else {
    970      // see RFC7616 for the description of this calculation
    971      var A1 = "guest:secret:guest";
    972      if (algorithm == "SHA-256-sess") {
    973        A1 = [HSHA256(A1), nonce, cnonce].join(":");
    974      }
    975      var A2 = "GET:/auth/" + test_name;
    976      var noncebits = [nonce, nc, cnonce, "auth", HSHA256(A2)].join(":");
    977      var digest = HSHA256([HSHA256(A1), noncebits].join(":"));
    978 
    979      if (clientDigest == digest) {
    980        response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
    981        body = "success";
    982      } else {
    983        send_401 = 1;
    984        body = "auth failed";
    985      }
    986    }
    987  } else {
    988    // no header, send one
    989    send_401 = 1;
    990    body = "failed, no header";
    991  }
    992 
    993  if (send_401) {
    994    var authenticate_sha256 =
    995      'Digest realm="secret", domain="/", qop=auth, ' +
    996      'algorithm=SHA-256, nonce="' +
    997      nonce +
    998      '", opaque="' +
    999      opaque +
   1000      '"';
   1001    var authenticate_sha256sess =
   1002      'Digest realm="secret", domain="/", qop=auth, ' +
   1003      'algorithm=SHA-256-sess, nonce="' +
   1004      nonce +
   1005      '", opaque="' +
   1006      opaque +
   1007      '"';
   1008    var authenticate_md5 =
   1009      'Digest realm="secret", domain="/", qop=auth, ' +
   1010      'algorithm=MD5, nonce="' +
   1011      nonce +
   1012      '", opaque="' +
   1013      opaque +
   1014      '"';
   1015    if (test_name == "digest_sha256") {
   1016      response.setHeader("WWW-Authenticate", authenticate_sha256, false);
   1017    } else if (test_name == "digest_sha256sess") {
   1018      response.setHeader("WWW-Authenticate", authenticate_sha256sess, false);
   1019    } else if (test_name == "digest_md5_sha256") {
   1020      response.setHeader("WWW-Authenticate", authenticate_md5, false);
   1021      response.setHeader("WWW-Authenticate", authenticate_sha256, true);
   1022    } else if (test_name == "digest_md5_sha256_oneline") {
   1023      response.setHeader(
   1024        "WWW-Authenticate",
   1025        authenticate_md5 + " " + authenticate_sha256,
   1026        false
   1027      );
   1028    } else if (test_name == "digest_sha256_md5") {
   1029      response.setHeader("WWW-Authenticate", authenticate_sha256, false);
   1030      response.setHeader("WWW-Authenticate", authenticate_md5, true);
   1031    }
   1032    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
   1033  }
   1034 
   1035  response.bodyOutputStream.write(body, body.length);
   1036 }
   1037 
   1038 function authDigestSHA256(metadata, response) {
   1039  authDigestSHA256_helper(metadata, response, "digest_sha256");
   1040 }
   1041 
   1042 function authDigestSHA256sess(metadata, response) {
   1043  authDigestSHA256_helper(metadata, response, "digest_sha256sess");
   1044 }
   1045 
   1046 function authDigestSHA256_MD5(metadata, response) {
   1047  authDigestSHA256_helper(metadata, response, "digest_sha256_md5");
   1048 }
   1049 
   1050 function authDigestMD5_SHA256(metadata, response) {
   1051  authDigestSHA256_helper(metadata, response, "digest_md5_sha256");
   1052 }
   1053 
   1054 function authDigestMD5_SHA256_oneline(metadata, response) {
   1055  authDigestSHA256_helper(metadata, response, "digest_md5_sha256_oneline");
   1056 }
   1057 
   1058 function authShortDigest(metadata, response) {
   1059  // no header, send one
   1060  response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
   1061  response.setHeader("WWW-Authenticate", "Digest", false);
   1062 }
   1063 
   1064 let buildLargePayload = (function () {
   1065  let size = 33 * 1024;
   1066  let ret = "";
   1067  return function () {
   1068    // Return cached value.
   1069    if (ret.length) {
   1070      return ret;
   1071    }
   1072    for (let i = 0; i < size; i++) {
   1073      ret += "a";
   1074    }
   1075    return ret;
   1076  };
   1077 })();
   1078 
   1079 function largeRealm(metadata, response) {
   1080  // test > 32KB realm tokens
   1081  var body;
   1082 
   1083  response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
   1084  response.setHeader(
   1085    "WWW-Authenticate",
   1086    'Digest realm="' + buildLargePayload() + '", domain="foo"'
   1087  );
   1088 
   1089  body = "need to authenticate";
   1090  response.bodyOutputStream.write(body, body.length);
   1091 }
   1092 
   1093 function largeDomain(metadata, response) {
   1094  // test > 32KB domain tokens
   1095  var body;
   1096 
   1097  response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
   1098  response.setHeader(
   1099    "WWW-Authenticate",
   1100    'Digest realm="foo", domain="' + buildLargePayload() + '"'
   1101  );
   1102 
   1103  body = "need to authenticate";
   1104  response.bodyOutputStream.write(body, body.length);
   1105 }
   1106 
   1107 add_task(async function test_large_realm() {
   1108  var chan = makeChan(URL + "/largeRealm", URL);
   1109 
   1110  listener.expectedCode = 401; // Unauthorized
   1111  await openAndListen(chan);
   1112 });
   1113 
   1114 add_task(async function test_large_domain() {
   1115  var chan = makeChan(URL + "/largeDomain", URL);
   1116 
   1117  listener.expectedCode = 401; // Unauthorized
   1118  await openAndListen(chan);
   1119 });
   1120 
   1121 async function add_parse_realm_testcase(testcase) {
   1122  httpserv.registerPathHandler("/parse_realm", (metadata, response) => {
   1123    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
   1124    response.setHeader("WWW-Authenticate", testcase.input, false);
   1125 
   1126    let body = "failed";
   1127    response.bodyOutputStream.write(body, body.length);
   1128  });
   1129 
   1130  let chan = makeChan(URL + "/parse_realm", URL);
   1131  let requestor = new RealmTestRequestor();
   1132  chan.notificationCallbacks = requestor;
   1133 
   1134  listener.expectedCode = 401;
   1135  await openAndListen(chan);
   1136  Assert.equal(requestor.promptRealm, testcase.realm);
   1137 }
   1138 
   1139 add_task(async function simplebasic() {
   1140  await add_parse_realm_testcase({
   1141    input: `Basic realm="foo"`,
   1142    scheme: `Basic`,
   1143    realm: `foo`,
   1144  });
   1145 });
   1146 
   1147 add_task(async function simplebasiclf() {
   1148  await add_parse_realm_testcase({
   1149    input: `Basic\r\n realm="foo"`,
   1150    scheme: `Basic`,
   1151    realm: `foo`,
   1152  });
   1153 });
   1154 
   1155 add_task(async function simplebasicucase() {
   1156  await add_parse_realm_testcase({
   1157    input: `BASIC REALM="foo"`,
   1158    scheme: `Basic`,
   1159    realm: `foo`,
   1160  });
   1161 });
   1162 
   1163 add_task(async function simplebasictok() {
   1164  await add_parse_realm_testcase({
   1165    input: `Basic realm=foo`,
   1166    scheme: `Basic`,
   1167    realm: `foo`,
   1168  });
   1169 });
   1170 
   1171 add_task(async function simplebasictokbs() {
   1172  await add_parse_realm_testcase({
   1173    input: `Basic realm=\\f\\o\\o`,
   1174    scheme: `Basic`,
   1175    realm: `\\foo`,
   1176  });
   1177 });
   1178 
   1179 add_task(async function simplebasicsq() {
   1180  await add_parse_realm_testcase({
   1181    input: `Basic realm='foo'`,
   1182    scheme: `Basic`,
   1183    realm: `'foo'`,
   1184  });
   1185 });
   1186 
   1187 add_task(async function simplebasicpct() {
   1188  await add_parse_realm_testcase({
   1189    input: `Basic realm="foo%20bar"`,
   1190    scheme: `Basic`,
   1191    realm: `foo%20bar`,
   1192  });
   1193 });
   1194 
   1195 add_task(async function simplebasiccomma() {
   1196  await add_parse_realm_testcase({
   1197    input: `Basic , realm="foo"`,
   1198    scheme: `Basic`,
   1199    realm: `foo`,
   1200  });
   1201 });
   1202 
   1203 add_task(async function simplebasiccomma2() {
   1204  await add_parse_realm_testcase({
   1205    input: `Basic, realm="foo"`,
   1206    scheme: `Basic`,
   1207    realm: ``,
   1208  });
   1209 });
   1210 
   1211 add_task(async function simplebasicnorealm() {
   1212  await add_parse_realm_testcase({
   1213    input: `Basic`,
   1214    scheme: `Basic`,
   1215    realm: ``,
   1216  });
   1217 });
   1218 
   1219 add_task(async function simplebasic2realms() {
   1220  await add_parse_realm_testcase({
   1221    input: `Basic realm="foo", realm="bar"`,
   1222    scheme: `Basic`,
   1223    realm: `foo`,
   1224  });
   1225 });
   1226 
   1227 add_task(async function simplebasicwsrealm() {
   1228  await add_parse_realm_testcase({
   1229    input: `Basic realm = "foo"`,
   1230    scheme: `Basic`,
   1231    realm: `foo`,
   1232  });
   1233 });
   1234 
   1235 add_task(async function simplebasicrealmsqc() {
   1236  await add_parse_realm_testcase({
   1237    input: `Basic realm="\\f\\o\\o"`,
   1238    scheme: `Basic`,
   1239    realm: `foo`,
   1240  });
   1241 });
   1242 
   1243 add_task(async function simplebasicrealmsqc2() {
   1244  await add_parse_realm_testcase({
   1245    input: `Basic realm="\\"foo\\""`,
   1246    scheme: `Basic`,
   1247    realm: `"foo"`,
   1248  });
   1249 });
   1250 
   1251 add_task(async function simplebasicnewparam1() {
   1252  await add_parse_realm_testcase({
   1253    input: `Basic realm="foo", bar="xyz",, a=b,,,c=d`,
   1254    scheme: `Basic`,
   1255    realm: `foo`,
   1256  });
   1257 });
   1258 
   1259 add_task(async function simplebasicnewparam2() {
   1260  await add_parse_realm_testcase({
   1261    input: `Basic bar="xyz", realm="foo"`,
   1262    scheme: `Basic`,
   1263    realm: `foo`,
   1264  });
   1265 });
   1266 
   1267 add_task(async function simplebasicrealmiso88591() {
   1268  await add_parse_realm_testcase({
   1269    input: `Basic realm="foo-ä"`,
   1270    scheme: `Basic`,
   1271    realm: `foo-ä`,
   1272  });
   1273 });
   1274 
   1275 add_task(async function simplebasicrealmutf8() {
   1276  await add_parse_realm_testcase({
   1277    input: `Basic realm="foo-ä"`,
   1278    scheme: `Basic`,
   1279    realm: `foo-ä`,
   1280  });
   1281 });
   1282 
   1283 add_task(async function simplebasicrealmrfc2047() {
   1284  await add_parse_realm_testcase({
   1285    input: `Basic realm="=?ISO-8859-1?Q?foo-=E4?="`,
   1286    scheme: `Basic`,
   1287    realm: `=?ISO-8859-1?Q?foo-=E4?=`,
   1288  });
   1289 });
   1290 
   1291 add_task(async function multibasicunknown() {
   1292  await add_parse_realm_testcase({
   1293    input: `Basic realm="basic", Newauth realm="newauth"`,
   1294    scheme: `Basic`,
   1295    realm: `basic`,
   1296  });
   1297 });
   1298 
   1299 add_task(async function multibasicunknownnoparam() {
   1300  await add_parse_realm_testcase({
   1301    input: `Basic realm="basic", Newauth`,
   1302    scheme: `Basic`,
   1303    realm: `basic`,
   1304  });
   1305 });
   1306 
   1307 add_task(async function multibasicunknown2() {
   1308  await add_parse_realm_testcase({
   1309    input: `Newauth realm="newauth", Basic realm="basic"`,
   1310    scheme: `Basic`,
   1311    realm: `basic`,
   1312  });
   1313 });
   1314 
   1315 add_task(async function multibasicunknown2np() {
   1316  await add_parse_realm_testcase({
   1317    input: `Newauth, Basic realm="basic"`,
   1318    scheme: `Basic`,
   1319    realm: `basic`,
   1320  });
   1321 });
   1322 
   1323 add_task(async function multibasicunknown2mf() {
   1324  httpserv.registerPathHandler("/parse_realm", (metadata, response) => {
   1325    response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
   1326    response.setHeader("WWW-Authenticate", `Newauth realm="newauth"`, false);
   1327    response.setHeader("WWW-Authenticate", `Basic realm="basic"`, false);
   1328 
   1329    let body = "failed";
   1330    response.bodyOutputStream.write(body, body.length);
   1331  });
   1332 
   1333  let chan = makeChan(URL + "/parse_realm", URL);
   1334  let requestor = new RealmTestRequestor();
   1335  chan.notificationCallbacks = requestor;
   1336 
   1337  listener.expectedCode = 401;
   1338  await openAndListen(chan);
   1339  Assert.equal(requestor.promptRealm, "basic");
   1340 });
   1341 
   1342 add_task(async function multibasicempty() {
   1343  await add_parse_realm_testcase({
   1344    input: `,Basic realm="basic"`,
   1345    scheme: `Basic`,
   1346    realm: `basic`,
   1347  });
   1348 });
   1349 
   1350 add_task(async function multibasicqs() {
   1351  await add_parse_realm_testcase({
   1352    input: `Newauth realm="apps", type=1, title="Login to \"apps\"", Basic realm="simple"`,
   1353    scheme: `Basic`,
   1354    realm: `simple`,
   1355  });
   1356 });
   1357 
   1358 add_task(async function multidisgscheme() {
   1359  await add_parse_realm_testcase({
   1360    input: `Newauth realm="Newauth Realm", basic=foo, Basic realm="Basic Realm"`,
   1361    scheme: `Basic`,
   1362    realm: `Basic Realm`,
   1363  });
   1364 });
   1365 
   1366 add_task(async function unknown() {
   1367  await add_parse_realm_testcase({
   1368    input: `Newauth param="value"`,
   1369    scheme: `Basic`,
   1370    realm: ``,
   1371  });
   1372 });
   1373 
   1374 add_task(async function parametersnotrequired() {
   1375  await add_parse_realm_testcase({ input: `A, B`, scheme: `Basic`, realm: `` });
   1376 });
   1377 
   1378 add_task(async function disguisedrealm() {
   1379  await add_parse_realm_testcase({
   1380    input: `Basic foo="realm=nottherealm", realm="basic"`,
   1381    scheme: `Basic`,
   1382    realm: `basic`,
   1383  });
   1384 });
   1385 
   1386 add_task(async function disguisedrealm2() {
   1387  await add_parse_realm_testcase({
   1388    input: `Basic nottherealm="nottherealm", realm="basic"`,
   1389    scheme: `Basic`,
   1390    realm: `basic`,
   1391  });
   1392 });
   1393 
   1394 add_task(async function missingquote() {
   1395  await add_parse_realm_testcase({
   1396    input: `Basic realm="basic`,
   1397    scheme: `Basic`,
   1398    realm: `basic`,
   1399  });
   1400 });