/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */

// checks for correct output with the default index handler, mostly to do
// escaping checks -- highly dependent on the default index handler output
// format

var srv, dir, gDirEntries;

ChromeUtils.defineLazyGetter(this, "BASE_URL", function () {
  return "http://localhost:" + srv.identity.primaryPort + "/";
});

function run_test() {
  createTestDirectory();

  srv = createServer();
  srv.registerDirectory("/", dir);

  var nameDir = do_get_file("data/name-scheme/");
  srv.registerDirectory("/bar/", nameDir);

  srv.start(-1);

  function done() {
    do_test_pending();
    destroyTestDirectory();
    srv.stop(function () {
      do_test_finished();
    });
  }

  runHttpTests(tests, done);
}

function createTestDirectory() {
  dir = Services.dirsvc.get("TmpD", Ci.nsIFile);
  dir.append("index_handler_test_" + Math.random());
  dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o744);

  // populate with test directories, files, etc.
  // Files must be in expected order of display on the index page!

  var files = [];

  makeFile("aa_directory", true, dir, files);
  makeFile("Ba_directory", true, dir, files);
  makeFile("bb_directory", true, dir, files);
  makeFile("foo", true, dir, files);
  makeFile("a_file", false, dir, files);
  makeFile("B_file", false, dir, files);
  makeFile("za'z", false, dir, files);
  makeFile("zb&z", false, dir, files);
  makeFile("zc<q", false, dir, files);
  makeFile('zd"q', false, dir, files);
  makeFile("ze%g", false, dir, files);
  makeFile("zf%200h", false, dir, files);
  makeFile("zg>m", false, dir, files);

  gDirEntries = [files];

  var subdir = dir.clone();
  subdir.append("foo");

  files = [];

  makeFile("aa_dir", true, subdir, files);
  makeFile("b_dir", true, subdir, files);
  makeFile("AA_file.txt", false, subdir, files);
  makeFile("test.txt", false, subdir, files);

  gDirEntries.push(files);
}

function destroyTestDirectory() {
  dir.remove(true);
}

/*************
 * UTILITIES *
 *************/

/** Verifies data in bytes for the trailing-caret path above. */
function hiddenDataCheck(bytes, uri, path) {
  var data = String.fromCharCode.apply(null, bytes);

  var parser = new DOMParser();

  // Note: the index format isn't XML -- it's actually HTML -- but we require
  //       the index format also be valid XML, albeit XML without namespaces,
  //       XML declarations, etc.  Doing this simplifies output checking.
  try {
    var doc = parser.parseFromString(data, "application/xml");
  } catch (e) {
    do_throw("document failed to parse as XML");
  }

  var body = doc.documentElement.getElementsByTagName("body");
  Assert.equal(body.length, 1);
  body = body[0];

  // header
  var header = body.getElementsByTagName("h1");
  Assert.equal(header.length, 1);

  Assert.equal(header[0].textContent, path);

  // files
  var lst = body.getElementsByTagName("ol");
  Assert.equal(lst.length, 1);
  var items = lst[0].getElementsByTagName("li");

  var top = Services.io.newURI(uri);

  // N.B. No ERROR_IF_SEE_THIS.txt^ file!
  var dirEntries = [
    { name: "file.txt", isDirectory: false },
    { name: "SHOULD_SEE_THIS.txt^", isDirectory: false },
  ];

  for (var i = 0; i < items.length; i++) {
    var link = items[i].childNodes[0];
    var f = dirEntries[i];

    var sep = f.isDirectory ? "/" : "";

    Assert.equal(link.textContent, f.name + sep);

    uri = Services.io.newURI(link.getAttribute("href"), null, top);
    Assert.equal(decodeURIComponent(uri.pathQueryRef), path + f.name + sep);
  }
}

/**
 * Verifies data in bytes (an array of bytes) represents an index page for the
 * given URI and path, which should be a page listing the given directory
 * entries, in order.
 *
 * @param bytes
 *   array of bytes representing the index page's contents
 * @param uri
 *   string which is the URI of the index page
 * @param path
 *   the path portion of uri
 * @param dirEntries
 *   sorted (in the manner the directory entries should be sorted) array of
 *   objects, each of which has a name property (whose value is the file's name,
 *   without / if it's a directory) and an isDirectory property (with expected
 *   value)
 */
function dataCheck(bytes, uri, path, dirEntries) {
  var data = String.fromCharCode.apply(null, bytes);

  var parser = new DOMParser();

  // Note: the index format isn't XML -- it's actually HTML -- but we require
  //       the index format also be valid XML, albeit XML without namespaces,
  //       XML declarations, etc.  Doing this simplifies output checking.
  try {
    var doc = parser.parseFromString(data, "application/xml");
  } catch (e) {
    do_throw("document failed to parse as XML");
  }

  var body = doc.documentElement.getElementsByTagName("body");
  Assert.equal(body.length, 1);
  body = body[0];

  // header
  var header = body.getElementsByTagName("h1");
  Assert.equal(header.length, 1);

  Assert.equal(header[0].textContent, path);

  // files
  var lst = body.getElementsByTagName("ol");
  Assert.equal(lst.length, 1);
  var items = lst[0].getElementsByTagName("li");
  var top = Services.io.newURI(uri);

  for (var i = 0; i < items.length; i++) {
    var link = items[i].childNodes[0];
    var f = dirEntries[i];

    var sep = f.isDirectory ? "/" : "";

    Assert.equal(link.textContent, f.name + sep);

    uri = Services.io.newURI(link.getAttribute("href"), null, top);
    Assert.equal(decodeURIComponent(uri.pathQueryRef), path + f.name + sep);
  }
}

/**
 * Create a file/directory with the given name underneath parentDir, and
 * append an object with name/isDirectory properties to lst corresponding
 * to it if the file/directory could be created.
 */
function makeFile(name, isDirectory, parentDir, lst) {
  var type = Ci.nsIFile[isDirectory ? "DIRECTORY_TYPE" : "NORMAL_FILE_TYPE"];
  var file = parentDir.clone();

  try {
    file.append(name);
    file.create(type, 0o755);
    lst.push({ name, isDirectory });
  } catch (e) {
    /* OS probably doesn't like file name, skip */
  }
}

/*********
 * TESTS *
 *********/

ChromeUtils.defineLazyGetter(this, "tests", function () {
  return [
    new Test(BASE_URL, null, start, stopRootDirectory),
    new Test(BASE_URL + "foo/", null, start, stopFooDirectory),
    new Test(
      BASE_URL + "bar/folder^/",
      null,
      start,
      stopTrailingCaretDirectory
    ),
  ];
});

// check top-level directory listing
function start(ch) {
  Assert.equal(ch.getResponseHeader("Content-Type"), "text/html;charset=utf-8");
}
function stopRootDirectory(ch, status, data) {
  dataCheck(data, BASE_URL, "/", gDirEntries[0]);
}

// check non-top-level, too
function stopFooDirectory(ch, status, data) {
  dataCheck(data, BASE_URL + "foo/", "/foo/", gDirEntries[1]);
}

// trailing-caret leaf with hidden files
function stopTrailingCaretDirectory(ch, status, data) {
  hiddenDataCheck(data, BASE_URL + "bar/folder^/", "/bar/folder^/");
}
