"use strict";

const { HttpServer } = ChromeUtils.importESModule(
  "resource://testing-common/httpd.sys.mjs"
);

var httpserver = new HttpServer();
httpserver.start(-1);

// Need to randomize, because apparently no one clears our cache
var suffix = Math.random();
var httpBase = "http://localhost:" + httpserver.identity.primaryPort;
var shortexpPath = "/shortexp" + suffix;
var longexpPath = "/longexp/" + suffix;
var longexp2Path = "/longexp/2/" + suffix;
var nocachePath = "/nocache" + suffix;
var nostorePath = "/nostore" + suffix;
var test410Path = "/test410" + suffix;
var test404Path = "/test404" + suffix;

var PrivateBrowsingLoadContext = Cu.createPrivateLoadContext();

function make_channel(url, flags, usePrivateBrowsing) {
  var securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;

  var uri = Services.io.newURI(url);
  var principal = Services.scriptSecurityManager.createContentPrincipal(uri, {
    privateBrowsingId: usePrivateBrowsing ? 1 : 0,
  });

  var req = NetUtil.newChannel({
    uri,
    loadingPrincipal: principal,
    securityFlags,
    contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER,
  });

  req.loadFlags = flags;
  if (usePrivateBrowsing) {
    req.notificationCallbacks = PrivateBrowsingLoadContext;
  }
  return req;
}

function Test(
  path,
  flags,
  expectSuccess,
  readFromCache,
  hitServer,
  usePrivateBrowsing /* defaults to false */
) {
  this.path = path;
  this.flags = flags;
  this.expectSuccess = expectSuccess;
  this.readFromCache = readFromCache;
  this.hitServer = hitServer;
  this.usePrivateBrowsing = usePrivateBrowsing;
}

Test.prototype = {
  flags: 0,
  expectSuccess: true,
  readFromCache: false,
  hitServer: true,
  usePrivateBrowsing: false,
  _buffer: "",
  _isFromCache: false,

  QueryInterface: ChromeUtils.generateQI([
    "nsIStreamListener",
    "nsIRequestObserver",
  ]),

  onStartRequest(request) {
    var cachingChannel = request.QueryInterface(Ci.nsICacheInfoChannel);
    this._isFromCache = request.isPending() && cachingChannel.isFromCache();
  },

  onDataAvailable(request, stream, offset, count) {
    this._buffer = this._buffer.concat(read_stream(stream, count));
  },

  onStopRequest(request, status) {
    Assert.equal(Components.isSuccessCode(status), this.expectSuccess);
    Assert.equal(this._isFromCache, this.readFromCache);
    Assert.equal(gHitServer, this.hitServer);

    do_timeout(0, run_next_test);
  },

  run() {
    dump(
      "Running:" +
        "\n  " +
        this.path +
        "\n  " +
        this.flags +
        "\n  " +
        this.expectSuccess +
        "\n  " +
        this.readFromCache +
        "\n  " +
        this.hitServer +
        "\n"
    );
    gHitServer = false;
    var channel = make_channel(this.path, this.flags, this.usePrivateBrowsing);
    channel.asyncOpen(this);
  },
};

var gHitServer = false;

var gTests = [
  new Test(
    httpBase + shortexpPath,
    0,
    true, // expect success
    false, // read from cache
    true, // hit server
    true
  ), // USE PRIVATE BROWSING, so not cached for later requests
  new Test(
    httpBase + shortexpPath,
    0,
    true, // expect success
    false, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + shortexpPath,
    0,
    true, // expect success
    true, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + shortexpPath,
    Ci.nsIRequest.LOAD_BYPASS_CACHE,
    true, // expect success
    false, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + shortexpPath,
    Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE,
    false, // expect success
    false, // read from cache
    false
  ), // hit server
  new Test(
    httpBase + shortexpPath,
    Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | Ci.nsIRequest.VALIDATE_NEVER,
    true, // expect success
    true, // read from cache
    false
  ), // hit server
  new Test(
    httpBase + shortexpPath,
    Ci.nsIRequest.LOAD_FROM_CACHE,
    true, // expect success
    true, // read from cache
    false
  ), // hit server

  new Test(
    httpBase + longexpPath,
    0,
    true, // expect success
    false, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + longexpPath,
    0,
    true, // expect success
    true, // read from cache
    false
  ), // hit server
  new Test(
    httpBase + longexpPath,
    Ci.nsIRequest.LOAD_BYPASS_CACHE,
    true, // expect success
    false, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + longexpPath,
    Ci.nsIRequest.VALIDATE_ALWAYS,
    true, // expect success
    true, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + longexpPath,
    Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE,
    true, // expect success
    true, // read from cache
    false
  ), // hit server
  new Test(
    httpBase + longexpPath,
    Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | Ci.nsIRequest.VALIDATE_NEVER,
    true, // expect success
    true, // read from cache
    false
  ), // hit server
  new Test(
    httpBase + longexpPath,
    Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | Ci.nsIRequest.VALIDATE_ALWAYS,
    false, // expect success
    false, // read from cache
    false
  ), // hit server
  new Test(
    httpBase + longexpPath,
    Ci.nsIRequest.LOAD_FROM_CACHE,
    true, // expect success
    true, // read from cache
    false
  ), // hit server

  new Test(
    httpBase + longexp2Path,
    0,
    true, // expect success
    false, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + longexp2Path,
    0,
    true, // expect success
    true, // read from cache
    false
  ), // hit server

  new Test(
    httpBase + nocachePath,
    0,
    true, // expect success
    false, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + nocachePath,
    0,
    true, // expect success
    true, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + nocachePath,
    Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE,
    false, // expect success
    false, // read from cache
    false
  ), // hit server

  // CACHE2: mayhemer - entry is doomed... I think the logic is wrong, we should not doom them
  // as they are not valid, but take them as they need to reval
  /*
  new Test(httpBase + nocachePath, Ci.nsIRequest.LOAD_FROM_CACHE,
           true,   // expect success
           true,   // read from cache
           false), // hit server
  */

  // LOAD_ONLY_FROM_CACHE would normally fail (because no-cache forces
  // a validation), but VALIDATE_NEVER should override that.
  new Test(
    httpBase + nocachePath,
    Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | Ci.nsIRequest.VALIDATE_NEVER,
    true, // expect success
    true, // read from cache
    false
  ), // hit server

  // ... however, no-cache over ssl should act like no-store and force
  // a validation (and therefore failure) even if VALIDATE_NEVER is
  // set.
  /* XXX bug 466524: We can't currently start an ssl server in xpcshell tests,
                     so this test is currently disabled.
  new Test(httpsBase + nocachePath,
           Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
           Ci.nsIRequest.VALIDATE_NEVER,
           false,  // expect success
           false,  // read from cache
           false)  // hit server
  */

  new Test(
    httpBase + nostorePath,
    0,
    true, // expect success
    false, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + nostorePath,
    0,
    true, // expect success
    false, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + nostorePath,
    Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE,
    false, // expect success
    false, // read from cache
    false
  ), // hit server
  new Test(
    httpBase + nostorePath,
    Ci.nsIRequest.LOAD_FROM_CACHE,
    true, // expect success
    true, // read from cache
    false
  ), // hit server
  // no-store should force the validation (and therefore failure, with
  // LOAD_ONLY_FROM_CACHE) even if VALIDATE_NEVER is set.
  new Test(
    httpBase + nostorePath,
    Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE | Ci.nsIRequest.VALIDATE_NEVER,
    false, // expect success
    false, // read from cache
    false
  ), // hit server

  new Test(
    httpBase + test410Path,
    0,
    true, // expect success
    false, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + test410Path,
    0,
    true, // expect success
    true, // read from cache
    false
  ), // hit server

  new Test(
    httpBase + test404Path,
    0,
    true, // expect success
    false, // read from cache
    true
  ), // hit server
  new Test(
    httpBase + test404Path,
    0,
    true, // expect success
    false, // read from cache
    true
  ), // hit server
];

function run_next_test() {
  if (!gTests.length) {
    httpserver.stop(do_test_finished);
    return;
  }

  var test = gTests.shift();
  test.run();
}

function handler(httpStatus, metadata, response) {
  gHitServer = true;
  let etag;
  try {
    etag = metadata.getHeader("If-None-Match");
  } catch (ex) {
    etag = "";
  }
  if (etag == "testtag") {
    // Allow using the cached data
    response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
  } else {
    response.setStatusLine(metadata.httpVersion, httpStatus, "Useless Phrase");
    response.setHeader("Content-Type", "text/plain", false);
    response.setHeader("ETag", "testtag", false);
    const body = "data";
    response.bodyOutputStream.write(body, body.length);
  }
}

function nocache_handler(metadata, response) {
  response.setHeader("Cache-Control", "no-cache", false);
  handler(200, metadata, response);
}

function nostore_handler(metadata, response) {
  response.setHeader("Cache-Control", "no-store", false);
  handler(200, metadata, response);
}

function test410_handler(metadata, response) {
  handler(410, metadata, response);
}

function test404_handler(metadata, response) {
  handler(404, metadata, response);
}

function shortexp_handler(metadata, response) {
  response.setHeader("Cache-Control", "max-age=0", false);
  handler(200, metadata, response);
}

function longexp_handler(metadata, response) {
  response.setHeader("Cache-Control", "max-age=10000", false);
  handler(200, metadata, response);
}

// test spaces around max-age value token
function longexp2_handler(metadata, response) {
  response.setHeader("Cache-Control", "max-age = 10000", false);
  handler(200, metadata, response);
}

function run_test() {
  httpserver.registerPathHandler(shortexpPath, shortexp_handler);
  httpserver.registerPathHandler(longexpPath, longexp_handler);
  httpserver.registerPathHandler(longexp2Path, longexp2_handler);
  httpserver.registerPathHandler(nocachePath, nocache_handler);
  httpserver.registerPathHandler(nostorePath, nostore_handler);
  httpserver.registerPathHandler(test410Path, test410_handler);
  httpserver.registerPathHandler(test404Path, test404_handler);

  run_next_test();
  do_test_pending();
}
